Skip to content

fix: preserve identifier quoting in CHECK constraints and fix BETWEEN parsing#11

Merged
tianzhou merged 4 commits intopgplex:mainfrom
screenfluent:fix/check-between-parsing
Sep 11, 2025
Merged

fix: preserve identifier quoting in CHECK constraints and fix BETWEEN parsing#11
tianzhou merged 4 commits intopgplex:mainfrom
screenfluent:fix/check-between-parsing

Conversation

@screenfluent
Copy link
Contributor

Fix identifier quoting in CHECK constraints and BETWEEN parsing

Problems Fixed

This PR addresses two critical issues with CHECK constraint parsing that affect schemas using camelCase naming (common in Better Auth, Prisma, and other modern ORMs):

1. CHECK constraints losing identifier quoting

Before:

-- Input: CONSTRAINT check_positive CHECK ("followerCount" >= 0)
-- Output: CHECK (followerCount >= 0)  -- WRONG: loses quotes!

After:

-- Output: CHECK ("followerCount" >= 0)  -- Correct: preserves quotes

2. BETWEEN incorrectly parsed

Before:

-- Input: CHECK ("stockLevel" BETWEEN 0 AND 1000)
-- Output: CHECK ("stockLevel" BETWEEN (0, 1000))  -- WRONG: tuple syntax!

After:

-- Output: CHECK ("stockLevel" BETWEEN 0 AND 1000)  -- Correct: proper AND syntax

Changes

internal/ir/parser.go

  • Added minimal quoteIdentifierIfNeeded() (~15 lines) to preserve camelCase identifiers
  • Modified extractColumnName() to quote identifiers when needed
  • Fixed parseAExpr() to handle BETWEEN with proper X AND Y syntax (not tuple)
  • Added wrapInParens() helper to ensure correct parentheses in CHECK expressions
  • Removed unnecessary parentheses around simple comparisons

internal/diff/table.go

  • Added ensureCheckClauseParens() as defensive measure in SQL generation
  • Ensures CHECK constraints always have proper outer parentheses

Tests

  • Added comprehensive tests in internal/diff/identifier_quote_test.go
  • Tests cover: camelCase, AND/OR expressions, BETWEEN, IN clauses

Impact

  • ✅ Fixes compatibility with Better Auth and similar ORMs using camelCase
  • ✅ All existing tests pass
  • ✅ Minimal code changes (~87 lines of production code)
  • ✅ No breaking changes

Testing

cd internal/diff
go test -run TestCheckConstraintQuoting -v

Note on Code Duplication

The quoteIdentifierIfNeeded() function introduces minor duplication with existing quoting logic in the diff package. This is intentional to keep the fix minimal and focused. A follow-up PR can extract this to a shared utility package to maintain DRY principles.

Related Issues

… parsing

This PR fixes two related issues with CHECK constraint parsing:

1. CHECK constraints with camelCase columns were losing their quotes
   - Added minimal quoteIdentifierIfNeeded() to preserve camelCase quoting
   - Modified extractColumnName() to quote identifiers when needed

2. BETWEEN expressions were incorrectly parsed as BETWEEN (X, Y) instead of BETWEEN X AND Y
   - Fixed parseAExpr() to handle BETWEEN with proper AND syntax
   - Removed unnecessary parentheses around simple comparisons

These changes ensure that CHECK constraints work correctly with camelCase column names
and complex expressions, which is critical for compatibility with modern ORMs like
Better Auth and Prisma.

Note: Aware of minor code duplication in quoting logic; planning to extract to
shared util package in follow-up PR to keep this fix focused and minimal.

Fixes identifier quoting issues reported with Better Auth integration.
Copilot AI review requested due to automatic review settings September 6, 2025 17:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes two critical issues with CHECK constraint parsing that affect schemas using camelCase naming conventions: preserving identifier quoting and correcting BETWEEN expression parsing.

  • Added identifier quoting preservation to maintain camelCase column names in CHECK constraints
  • Fixed BETWEEN parsing to use proper X AND Y syntax instead of incorrect tuple syntax
  • Added defensive parentheses handling for CHECK clause generation

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
internal/ir/parser.go Added identifier quoting logic and fixed BETWEEN parsing in expression handling
internal/diff/table.go Added defensive CHECK clause parentheses normalization in SQL generation
internal/diff/identifier_quote_test.go Added comprehensive test cases for CHECK constraint quoting scenarios

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Contributor

@tianzhou tianzhou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, could you please

  1. Resolve the conflict (due to another PR that you authored)
  2. Run the full test to make sure we don't break anything "go test -v ./..."

@tianzhou
Copy link
Contributor

tianzhou commented Sep 8, 2025

I have to admit that I haven't thought about this identifier case issue in the first place. Do you have a minimal example that I can follow to reproduce the issue? I would like to add a comprehensive test case.

@screenfluent
Copy link
Contributor Author

Sorry, I'm bit busy. Hopefully tomorrow I can work on this. All I was trying to do was to use Better Auth library with SvelteKit project + pgschema. Better Auth has CLI tool to generate schema and uses camelCase. That's why I ended up needing to handle mixed case identifiers.

Here's a minimal example to reproduce the issue:

-- Better Auth generates tables with camelCase
CREATE TABLE "user" (
    "id" TEXT PRIMARY KEY,
    "email" TEXT UNIQUE,
    "emailVerified" BOOLEAN
);

CREATE TABLE "session" (
    "id" TEXT PRIMARY KEY,
    "userId" TEXT REFERENCES "user"("id"),
    "expiresAt" TIMESTAMP
);

-- The issue appears when CHECK constraints reference these columns
ALTER TABLE "user" 
ADD CONSTRAINT "email_check" 
CHECK ("emailVerified" IS NOT NULL);

The problem is that pgschema doesn't preserve the quotes around camelCase identifiers in CHECK constraints. Without quotes, PostgreSQL converts them to lowercase and then can't find the column.

Expected output should preserve quotes:

CHECK ("emailVerified" IS NOT NULL)  -- correct

Not:


CHECK (emailVerified IS NOT NULL)  -- loses quotes, breaks


Let me know if you need more details or a different test case!

- Merge upstream main and resolve conflicts in identifier_quote_test.go
- Improve hardcoded logic in ensureCheckClauseParens using depth-based validation
- Remove duplicate quoteIdentifierIfNeeded function, use util.QuoteIdentifier instead
- Fix CHECK constraint parsing for domains and expressions
- Add parentheses around comparison operators to match PostgreSQL's internal format
- Fix unused import issue

All tests now pass (100% success rate).

Addresses maintainer's feedback about hardcoded logic and resolves merge conflicts.
@screenfluent screenfluent force-pushed the fix/check-between-parsing branch from 66b5a6e to 3f739f4 Compare September 10, 2025 15:32
@screenfluent
Copy link
Contributor Author

@tianzhou conflicts resolved and hardcoded issues fixed. Using depth-based validation now instead of hardcoded checks.

Copy link
Contributor

@tianzhou tianzhou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tianzhou tianzhou merged commit 8b6b027 into pgplex:main Sep 11, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants